/****************************************************************************
 Copyright (c) 2017-2018 SuperSuRaccoon
 
 Site: http://www.supersuraccoon-cocos2d.com
 Mail: supersuraccoon@gmail.com

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/

const ssr = require('../namespace/SVGNamespace');

ssr.SVG.WriterComponent = cc.Class({
    extends: cc.Component,
    editor: CC_EDITOR && {
        menu: 'ssr/SVG/WriterComponent',
        executeInEditMode: true,
    },
    properties: {
        // 用于绘制汉字主体
        _hanziRender: cc.Graphics,
        // 用于绘制遮罩蒙版
        _maskRender: cc.Graphics,
        // 用于绘制笔画填充内容
        _fillRender: cc.Graphics,
        // 用于绘制汉字测试填充内容
        _testRender: cc.Graphics,
        // 用于绘制汉字测试提示内容
        _hintRender: cc.Graphics,
        // 用于绘汉字边框
        _frameRender: cc.Graphics,
        // 用于绘汉字网格线背景
        _gridRender: cc.Graphics,
        // 用于绘制汉字笔顺引导线
        _mediansRender: cc.Graphics,
        //
        _hzWriterJson: {
            default: null,
            type: cc.JsonAsset
        },
        hzWriterJson: {
            get: function () {
                return this._hzWriterJson;
            },
            set: function (value) {
                this._hzWriterJson = value;
                this.updateSVGObject();
                if (CC_EDITOR) {
                    this.redrawAll();
                }
            },
            type: cc.JsonAsset,
            tooltip: "汉字 JSON 数据文件"
        },
        _segments: 6,
        segments: {
            get: function () {
                return this._segments;
            },
            set: function (value) {
                this._segments = value;
                this.updateSVGObject();
                if (CC_EDITOR) {
                    this.redrawAll();
                }
            },
            type: cc.Integer,
            tooltip: "渲染汉字的平滑程度，值越大约平滑，但是效率会降低"
        },
        _flipX: false,
        flipX: {
            get: function () {
                return this._flipX;
            },
            set: function (value) {
                this._flipX = value;
                this.updateConfiguration();
                this.drawHanzi();
            },
            tooltip: "纹理是否水平翻转"
        },
        _flipY: true,
        flipY: {
            get: function () {
                return this._flipY;
            },
            set: function (value) {
                this._flipY = value;
                this.updateConfiguration();
                this.drawHanzi();
            },
            tooltip: "纹理是否垂直翻转"
        },
         _showGrid: true,
        showGrid: {
            get: function () {
                return this._showGrid;
            },
            set: function (value) {
                this._showGrid = value;
                this.drawGrid();
            },
            type: cc.Boolean,
            tooltip: "是否显示网格线"
        },
        _gridColor: cc.Color.BLACK,
        gridColor: {
            get () {
                return this._gridColor.clone();
            },
            set (value) {
                if (!this._gridColor.equals(value)) {
                    this._gridColor.set(value);
                    if (this._gridRender) {
                        this._gridRender.strokeColor = value;
                    }
                    this.drawGrid();
                }
            },
            tooltip: "网格线颜色"
        },
        _gridWidth: 4,
        gridWidth: {
            get () {
                return this._gridWidth;
            },
            set (value) {
                if (this._gridWidth != value) {
                    this._gridWidth = value;
                    if (this._gridRender) {
                        this._gridRender.lineWidth = value;
                    }
                    this.drawGrid();
                }
            },
            tooltip: "网格线宽度"
        },
        _gridLength: 4,
        gridLength: {
            get () {
                return this._gridLength;
            },
            set (value) {
                if (this._gridLength != value && value > 0) {
                    this._gridLength = value;
                    if (this._gridRender) {
                        this._gridRender.__gridLength = value;
                    }
                    this.drawGrid();
                }
            },
            tooltip: "网格线虚线单位长度"
        },
        _showFrame: true,
        showFrame: {
            get: function () {
                return this._showFrame;
            },
            set: function (value) {
                this._showFrame = value;
                this.drawFrame();
            },
            type: cc.Boolean,
            tooltip: "是否显示边框"
        },
        _frameColor: cc.Color.BLACK,
        frameColor: {
            get () {
                return this._frameColor.clone();
            },
            set (value) {
                if (!this._frameColor.equals(value)) {
                    this._frameColor.set(value);
                    if (this._frameRender) {
                        this._frameRender.strokeColor = value;
                    }
                    this.drawFrame();
                }
            },
            tooltip: "边框颜色"
        },
        _frameFillColor: cc.color(0, 0, 0, 0),
        frameFillColor: {
            get () {
                return this._frameFillColor.clone();
            },
            set (value) {
                if (!this._frameFillColor.equals(value)) {
                    this._frameFillColor.set(value);
                    if (this._frameRender) {
                        this._frameRender.fillColor = value;
                    }
                    this.drawFrame();
                }
            },
            tooltip: "边框内部填充颜色"
        },
        _frameWidth: 4,
        frameWidth: {
            get () {
                return this._frameWidth;
            },
            set (value) {
                if (this._frameWidth != value) {
                    this._frameWidth = value;
                    if (this._frameRender) {
                        this._frameRender.lineWidth = value;
                    }
                    this.drawFrame();
                }
            },
            tooltip: "边框宽度"
        },
        _framePadding: 20,
        framePadding: {
            get () {
                return this._framePadding;
            },
            set (value) {
                if (this._framePadding != value) {
                    this._framePadding = value;
                    this.drawFrame();
                    this.drawGrid();
                }
            },
            tooltip: "边框边距"
        },
        _showMedians: true,
        showMedians: {
            get: function () {
                return this._showMedians;
            },
            set: function (value) {
                this._showMedians = value;
                this.drawMedians();
            },
            type: cc.Boolean,
            tooltip: "是否显示笔画引导中线"
        },
        _mediansColor: cc.Color.BLACK,
        mediansColor: {
            get () {
                return this._mediansColor.clone();
            },
            set (value) {
                if (!this._mediansColor.equals(value)) {
                    this._mediansColor.set(value);
                    if (this._mediansRender) {
                        this._mediansRender.strokeColor = value;
                    }
                    this.drawMedians();
                }
            },
            tooltip: "笔画引导中线颜色"
        },
        _mediansWidth: 4,
        mediansWidth: {
            get () {
                return this._mediansWidth;
            },
            set (value) {
                if (this._mediansWidth != value) {
                    this._mediansWidth = value;
                    if (this._mediansRender) {
                        this._mediansRender.lineWidth = value;
                    }
                    this.drawMedians();
                }
            },
            tooltip: "笔画引导中线宽度"
        },
        //
        _showMediansControlPoint: true,
        showMediansControlPoint: {
            get: function () {
                return this._showMediansControlPoint;
            },
            set: function (value) {
                this._showMediansControlPoint = value;
                this.drawMedians();
            },
            type: cc.Boolean,
            tooltip: "是否显示笔画引导中线"
        },
        _mediansControlPointColor: cc.Color.BLACK,
        mediansControlPointColor: {
            get () {
                return this._mediansControlPointColor.clone();
            },
            set (value) {
                if (!this._mediansControlPointColor.equals(value)) {
                    this._mediansControlPointColor.set(value);
                    if (this._mediansRender) {
                        this._mediansRender.fillColor = value;
                    }
                    this.drawMedians();
                }
            },
            tooltip: "笔画引导中线颜色"
        },
        _mediansControlPointSize: 4,
        mediansControlPointSize: {
            get () {
                return this._mediansControlPointSize;
            },
            set (value) {
                if (this._mediansControlPointSize != value) {
                    this._mediansControlPointSize = value;
                    this.drawMedians();
                }
            },
            tooltip: "笔画引导中线宽度"
        },
        //
        _hanziOutlineColor: cc.Color.BLACK,
        hanziOutlineColor: {
            get () {
                return this._hanziOutlineColor.clone();
            },
            set (value) {
                if (!this._hanziOutlineColor.equals(value)) {
                    this._hanziOutlineColor.set(value);
                    if (this._hanziRender) {
                        this._hanziRender.strokeColor = value;
                    }
                    this.drawHanzi();
                }
            },
            tooltip: "汉字描边颜色"
        },
        _hanziOutlineWidth: 4,
        hanziOutlineWidth: {
            get () {
                return this._hanziOutlineWidth;
            },
            set (value) {
                if (this._hanziOutlineWidth != value) {
                    this._hanziOutlineWidth = value;
                    if (this._hanziRender) {
                        this._hanziRender.lineWidth = value;
                    }
                    this.drawHanzi();
                }
            },
            tooltip: "汉字描边宽度"
        },
        _hanziFillColor: cc.color(0, 0, 0, 0),
        hanziFillColor: {
            get () {
                return this._hanziFillColor.clone();
            },
            set (value) {
                if (!this._hanziFillColor.equals(value)) {
                    this._hanziFillColor.set(value);
                    if (this._hanziRender) {
                        this._hanziRender.fillColor = value;
                    }
                    this.drawHanzi();
                }
            },
            tooltip: "汉字填充颜色"
        },
        //
        _testAccuracy: 20,
        testAccuracy: {
            get () {
                return this._testAccuracy;
            },
            set (value) {
                if (this._testAccuracy != value) {
                    this._testAccuracy = value;
                }
            },
            tooltip: "汉字测试判定精准度"
        },
        _hintColor: cc.color(0, 0, 0, 0),
        hintColor: {
            get () {
                return this._hintColor.clone();
            },
            set (value) {
                if (!this._hintColor.equals(value)) {
                    this._hintColor.set(value);
                    if (this._hintRender) {
                        this._hintRender.fillColor = value;
                    }
                }
            },
            tooltip: "汉字笔画提示用颜色"
        },
         _hintSpeed: 0.2,
        hintSpeed: {
            get () {
                return this._hintSpeed;
            },
            set (value) {
                if (this._hintSpeed != value) {
                    this._hintSpeed = value;
                }
            },
            tooltip: "汉字测试笔画提示速度"
        },
        //
        _strokeColor: cc.color(0, 0, 0, 0),
        strokeColor: {
            get () {
                return this._strokeColor.clone();
            },
            set (value) {
                if (!this._strokeColor.equals(value)) {
                    this._strokeColor.set(value);
                    if (this._fillRender) {
                        this._fillRender.strokeColor = value;
                    }
                }
            },
            tooltip: "笔画动画颜色"
        },
        _strokeWidth: 40,
        strokeWidth: {
            get () {
                return this._strokeWidth;
            },
            set (value) {
                if (this._strokeWidth != value && value > 0) {
                    this._strokeWidth = value;
                    if (this._fillRender) {
                        this._fillRender.lineWidth = value;
                    }
                }
            },
            tooltip: "笔画动画宽度，注意宽度要足够"
        },
        /**
         * Callback
         */
        cbOneStrokeStart: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "新的一笔，动画开始渲染，回调函数"
        },
        cbOneStrokeMove: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "一笔动画正在渲染中，回调函数"
        },
        cbOneStrokeEnd: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "一笔动画渲染结束，回调函数"
        },
        cbOneMedianStart: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "新的一笔，动画开始渲染，回调函数"
        },
        cbOneMedianMove: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "新的一笔，动画开始渲染，回调函数"
        },
        cbOneMedianEnd: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "新的一笔，动画开始渲染，回调函数"
        },
        cbStrokeStart: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "笔画动画渲染开始，回调函数"
        },
        cbStrokeEnd: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "笔画动画渲染全部结束，回调函数"
        },
        cbTestStart: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "汉字测试开始，回调函数"
        },
        cbTestMove: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "汉字测试移动中，回调函数"
        },
        cbTestEnd: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "汉字测试移动结束，回调函数"
        },
        cbTestOneStrokeStart: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "汉字测试中，开始一个新的笔画，回调函数"
        },
        cbTestOneStrokeEnd: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "汉字测试中，结束一个笔画，回调函数"
        },
        cbTestOneMedianStart: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "汉字测试中，开始一个新的笔画，回调函数"
        },
        cbTestOneMedianFinish: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "汉字测试中，开始一个新的笔画，回调函数"
        },
        cbTestOneMedianEnd: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "汉字测试中，开始一个新的笔画，回调函数"
        },
        cbTestHintStart: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "汉字测试提示开始，回调函数"
        },
        cbTestHintEnd: {
            default: [],
            type: cc.Component.EventHandler,
            tooltip: "汉字测试提示结束，回调函数"
        },
    },

    onLoad () {

    },

    _syncSize () {
        let size = this.node.getContentSize();
        this._updateContentSize();
        this.redrawAll();
    },

    start () {

        this.node.on(cc.Node.EventType.SIZE_CHANGED, this._syncSize, this);
        /*
            https://github.com/skishore/makemeahanzi => strokes 
            The upper-left corner is at position (0, 900).
            The lower-right corner is at position (1024, -124).
        */
        this.__dataWidth = 1024;
        this.__dataHeight = 1024;
        this.__dataScale = 1.0;

        if (this.node.getChildByName("HanziRender")) {
            this._hanziRender = this.node.getChildByName("HanziRender").getComponent(cc.Graphics);    
        }        
        if (this.node.getChildByName("GridRender")) {
            this._gridRender = this.node.getChildByName("GridRender").getComponent(cc.Graphics);    
        }        
        if (this.node.getChildByName("FrameRender")) {
            this._frameRender = this.node.getChildByName("FrameRender").getComponent(cc.Graphics);    
        }        
        if (this.node.getChildByName("TestRender")) {
            this._testRender = this.node.getChildByName("TestRender").getComponent(cc.Graphics);    
        }        
        if (this.node.getChildByName("HintRender")) {
            this._hintRender = this.node.getChildByName("HintRender").getComponent(cc.Graphics);    
        }
        if (this.node.getChildByName("MediansRender")) {
            this._mediansRender = this.node.getChildByName("MediansRender").getComponent(cc.Graphics);    
        }        
        if (this.node.getChildByName("MaskRender")) {
            this._maskRender = this.node.getChildByName("MaskRender").getComponent(cc.Mask)._graphics;    
            this._fillRender = this.node.getChildByName("MaskRender").getChildByName("FillRender").getComponent(cc.Graphics);
            this._fillRender.strokeColor = this.strokeColor;
        }
        //
        this.resetRender();
        this._commandCount = 0;
        this._areaCount = 0;
        this.updateSVGObject();
        if (CC_EDITOR && this.hzWriterJson) {
            this.drawAll();
        }
    },

    load (hanzi, callback) {
        let self = this;
        cc.resources.load("hanzi-writer-data/" + hanzi, function (err, jsonAsset) {
            if (!err) {
                self.hzWriterJson = jsonAsset;
            }
            if (callback) {
                callback(err);
            }
        });
    },

    // 绘制整个汉字
    drawHanzi () {
        this._hanziRender.clear();
        for (var i = 0; i < this._svgObject.commandArray.length; i++) {
            let command = this._svgObject.commandArray[i];
            for (var j = 0; j < command.areaArray.length; j++) {
                let area = command.areaArray[j];
                for (var k = 0; k < area.strokeArray.length; k++) {
                    ssr.SVG.Util.Render.draw(this._hanziRender, area.strokeArray[k], this.configuration, false);
                }
            }
        }
    },

    // 绘制整个汉字的笔顺引导线
    drawMedians () {
        if (!this._mediansRender) {
            return;
        }
        this._mediansRender.clear();
        if (!this.showMedians) {
            return;
        }
        let oldCommandIndex = this._commandIndex;
        let oldMedianIndex = this._medianIndex;
        this._commandIndex = 0;
        while(this._drawMedian()) {};
        this._commandIndex = oldCommandIndex;
        this._medianIndex = oldMedianIndex;
        this._svgCommandObject = this._svgObject.commandArray[this._commandIndex - 1];
    },

    // 绘制汉字的轮廓
    drawFrame () {
        if (!this._frameRender) {
            return;
        }
        this._frameRender.clear();
        //
        let max = Math.max(this.node.width, this.node.height) + this.framePadding;
        if (this.showFrame) {
            this._frameRender.rect(-max / 2, -max / 2, max, max);
            this._frameRender.stroke();
            this._frameRender.fill();
        }
    },

    // 绘制汉字的网格线
    drawGrid () {
        if (!this._gridRender) {
            return;
        }
        this._gridRender.clear();
        //
        let max = Math.max(this.node.width, this.node.height) + this.framePadding;
        if (this.showGrid) {
            let segments = max / this.gridLength;
            //
            for (var i = 0; i < segments; i += 2) {
                this._gridRender.moveTo(-max / 2 + this.gridLength * i, 0);
                if (i + 1 > segments) {
                    this._gridRender.lineTo(max / 2, 0);
                }
                else {
                    this._gridRender.lineTo(-max / 2 + this.gridLength * (i + 1), 0);
                }
            }
            //
            for (var i = 0; i < segments; i += 2) {
                this._gridRender.moveTo(0, -max / 2 + this.gridLength * i);
                if (i + 1 > segments) {
                    this._gridRender.lineTo(0, max / 2);
                }
                else {
                    this._gridRender.lineTo(0, -max / 2 + this.gridLength * (i + 1));   
                }
            }
            //
            for (var i = 0; i < segments; i += 2) {
                this._gridRender.moveTo(-max / 2 + this.gridLength * i, -max / 2 + this.gridLength * i);
                if (i + 1 > segments) {
                    this._gridRender.lineTo(max / 2, max / 2);
                }
                else {
                    this._gridRender.lineTo(-max / 2 + this.gridLength * (i + 1), -max / 2 + this.gridLength * (i + 1));   
                }
            }
            //
            for (var i = 0; i < segments; i += 2) {
                this._gridRender.moveTo(-max / 2 + this.gridLength * i, max / 2 - this.gridLength * i);
                if (i + 1 > segments) {
                    this._gridRender.lineTo(max / 2, -max / 2);
                }
                else {
                    this._gridRender.lineTo(-max / 2 + this.gridLength * (i + 1), max / 2 - this.gridLength * (i + 1));   
                }
            }
            this._gridRender.stroke();
        }
    },

    // 绘制汉字的一个引导线
    _drawMedian () {
        // mediansRender
        if (this._commandIndex > this._commandCount) {
            return false;
        }
        if (this._commandIndex == 0) {
            this._commandIndex += 1;
            this._svgCommandObject = this._svgObject.commandArray[this._commandIndex - 1];
            this._medianCount = this._svgCommandObject.median.length;
            this._medianIndex = 0;
        }
        this._medianIndex += 1;
        this._median = this._svgCommandObject.median[this._medianIndex - 1];
        let x = (this.configuration.flipX ? -this._median[0] * this.configuration.dataScale + this.configuration.offset.x : this._median[0] * this.configuration.dataScale - this.configuration.offset.x);
        let y = (this.configuration.flipY ? -this._median[1] * this.configuration.dataScale + this.configuration.offset.y : this._median[1] * this.configuration.dataScale - this.configuration.offset.y);

        if (this._medianIndex == 1) {
            if (this.showMediansControlPoint) {
                this._mediansRender.circle(x, y, this.mediansControlPointSize);
                this._mediansRender.fill();
            }
            this._mediansRender.moveTo(x, y);
        }
        else {
            this._mediansRender.lineTo(x, y);
            this._mediansRender.stroke();
            if (this.showMediansControlPoint) {
                this._mediansRender.circle(x, y, this.mediansControlPointSize);
                this._mediansRender.fill();
            }
            this._mediansRender.moveTo(x, y);
        }
        if (this._medianIndex >= this._medianCount) {
            //
            this._commandIndex += 1;
            this._svgCommandObject = this._svgObject.commandArray[this._commandIndex - 1];
            if (!this._svgCommandObject) {
                return false;
            }
            this._medianCount = this._svgCommandObject.median.length;
            this._medianIndex = 0;
        }
        return true;
    },

    _drawMask () {
        if (!this._maskRender) {
            return;
        }
        for (var i = 0; i < this._svgCommandObject.areaArray.length; i++) {
            let area = this._svgCommandObject.areaArray[i];
            for (var j = 0; j < area.strokeArray.length; j++) {
                ssr.SVG.Util.Render.draw(this._maskRender, area.strokeArray[j], this.configuration, false);
            }
        }
    },

    _drawHanziCommand () {
        if (!this._hanziRender) {
            return;
        }
        for (var i = 0; i < this._svgCommandObject.areaArray.length; i++) {
            let area = this._svgCommandObject.areaArray[i];
            for (var j = 0; j < area.strokeArray.length; j++) {
                ssr.SVG.Util.Render.draw(this._hanziRender, area.strokeArray[j], this.configuration, false);
            }
        }
    },

    drawAll () {
        this.drawFrame();
        this.drawGrid();
        this.drawHanzi();
        this.drawMedians();
    },

    redrawAll () {
        this.resetRender();
        this.updateConfiguration();
        this.drawAll();
    },

    _fillTestStroke (commandIndex) {
        this._svgCommandObject = this._svgObject.commandArray[commandIndex - 1];
        this._hanziRender.lineWidth = 0;
        this._hanziRender.strokeColor = cc.color(0, 0, 0, 0);
        this._hanziRender.fillColor = this._fillRender.strokeColor;
        this._drawHanziCommand();
    },

    resetStroke () {
        this._maskRender.clear();
        this._fillRender.clear();
        this._hanziRender.fillColor = this.hanziFillColor;
        this.drawHanzi();
        this._commandIndex = 0;
        for (var i = 0; i < this._svgObject.commandArray.length; i++) {
            this._svgObject.commandArray[i].isFinished = false;
        }
    },

    stroke (commandIndex) {
        if (!commandIndex) {
            commandIndex = this._commandIndex;
        }
        else {
            this._commandIndex = commandIndex;
        }
        if (commandIndex > this._commandCount) {
            return;
        }
        if (commandIndex == 0) {
            this._commandIndex = commandIndex = 1;
        }
        this._svgCommandObject = this._svgObject.commandArray[commandIndex - 1];
        this._hanziRender.lineWidth = 0;
        this._hanziRender.strokeColor = cc.color(0, 0, 0, 0);
        this._hanziRender.fillColor = this._fillRender.strokeColor;
        this._drawHanziCommand();
        this._svgCommandObject.isFinished = true;
        this._commandIndex += 1;
    },

    isStroked (commandIndex) {
        if (!commandIndex) {
            return false;
        }
        return this._svgObject.commandArray[commandIndex - 1].isFinished;
    },

    isAllStrokeFinished () {
        for (var i = 0; i < this._svgObject.commandArray.length; i++) {
            let command = this._svgObject.commandArray[i];
            if (!command.isFinished) {
                return false;
            }
        }
        return true;
    },

    randomStroke () {
        if (this.isAllStrokeFinished()) {
            return;
        }
        let commandIndex = Math.floor(Math.random() * this._commandCount) + 1;
        while (this.isStroked(commandIndex)) {
            commandIndex = Math.floor(Math.random() * this._commandCount) + 1;
        }
        this.stroke(commandIndex);
    },

    randomAnimateStroke (speed) {
        if (this.isAllStrokeFinished()) {
            return;
        }
        let commandIndex = Math.floor(Math.random() * this._commandCount) + 1;
        while (this.isStroked(commandIndex)) {
            commandIndex = Math.floor(Math.random() * this._commandCount) + 1;
        }
        this.animateStroke(commandIndex, speed);
    },

    randomAnimateReset () {
        this.resetStroke();
    },

    _animateStrokeTimer () {
        this._medianIndex += 1;
        this._median = this._svgCommandObject.median[this._medianIndex - 1];
        let x = (this.flipX ? -this._median[0] * this.configuration.dataScale + offset.x : this._median[0] * this.configuration.dataScale - this.offset.x);
        let y = (this.flipY ? -this._median[1] * this.configuration.dataScale + offset.y : this._median[1] * this.configuration.dataScale - this.offset.y);
        //
        if (this._medianIndex == 1) {
            this._fillRender.moveTo(x, y);
            //
            cc.Component.EventHandler.emitEvents(this.cbOneStrokeMove, this, {"index": this._medianIndex, "pos": {"x": x.toFixed(2), "y": y.toFixed(2)}});
            cc.Component.EventHandler.emitEvents(this.cbOneMedianStart, this, {"index": this._medianIndex, "pos": {"x": x.toFixed(2), "y": y.toFixed(2)}});
        }
        else {
            this._fillRender.lineTo(x, y);
            this._fillRender.stroke();
            //
            cc.Component.EventHandler.emitEvents(this.cbOneStrokeMove, this, {"index": this._medianIndex, "pos": {"x": x.toFixed(2), "y": y.toFixed(2)}});
            cc.Component.EventHandler.emitEvents(this.cbOneMedianMove, this, {"index": this._medianIndex, "pos": {"x": x.toFixed(2), "y": y.toFixed(2)}});
        }
        if (this._medianIndex >= this._medianCount) {
            this.unschedule(this._animateStrokeTimer);
            //
            this._hanziRender.lineWidth = 0;
            this._hanziRender.strokeColor = cc.color(0, 0, 0, 0);
            this._hanziRender.fillColor = this._fillRender.strokeColor;
            this._drawHanziCommand();
            this._svgCommandObject.isFinished = true;
            //
            cc.Component.EventHandler.emitEvents(this.cbOneMedianEnd, this, {"index": this._medianIndex});
            cc.Component.EventHandler.emitEvents(this.cbOneStrokeEnd, this, {"index": this._commandIndex});
            //
            this._commandIndex += 1;
        }
    },

    // 填充汉字的下个笔顺内容 
    animateStroke (commandIndex, speed) {
        if (!commandIndex) {
            commandIndex = this._commandIndex;
        }
        else {
            this._commandIndex = commandIndex;
        }
        if (commandIndex > this._commandCount) {
            this._maskRender.clear();
            this._fillRender.clear();
            cc.Component.EventHandler.emitEvents(this.cbStrokeEnd, this);
            return false;
        }
        if (commandIndex == 0) {
            this._commandIndex = commandIndex = 1;
            this._svgCommandObject = this._svgObject.commandArray[commandIndex - 1];
            this._drawMask();
            this._medianCount = this._svgCommandObject.median.length;
            this._medianIndex = 0;
        }
        else {
            this._svgCommandObject = this._svgObject.commandArray[commandIndex - 1];
        }
        cc.Component.EventHandler.emitEvents(this.cbStrokeStart, this);
        cc.Component.EventHandler.emitEvents(this.cbOneStrokeStart, this, {"index": commandIndex});
        //
        this._medianCount = this._svgCommandObject.median.length;
        this._medianIndex = 0;
        this._maskRender.clear();
        this._fillRender.clear();
        this._drawMask();
        speed = speed || 0;
        this.schedule(this._animateStrokeTimer, speed);
    },

    getSVGObject () {
        return this._svgObject;
    },

    getCommandIndex () {
        return this._commandIndex;
    },
    getCommandCount () {
        return this._commandCount;
    },
    getCommandObject () {
        return this._svgCommandObject;
    },
    
    resetRender () {
        if (this._hanziRender) {
            this._hanziRender.clear();
        }
        if (this._gridRender) {
            this._gridRender.clear();
        }
        if (this._frameRender) {
            this._frameRender.clear();
        }
        if (this._mediansRender) {
            this._mediansRender.clear();
        }
        if (this._maskRender) {
            this._maskRender.clear();
        }
        if (this._fillRender) {
            this._fillRender.clear();
        }
        //
        this._testCommandIndex = 0;
        this._testMedicanIndex = 0;
        this._testMedicansHitArray = [];
        this._isInTest = false;
        this._isWaitingForTest = false;
        //
        this._commandIndex = 0;
        this._areaIndex = 0;
        this._strokeIndex = 0;
        this._medianIndex = 0;
    },

    resetHanziRender () {
        if (this._hanziRender) {
            this._hanziRender.clear();
        }
        if (this._mediansRender) {
            this._mediansRender.clear();
        }
        if (this._maskRender) {
            this._maskRender.clear();
        }
        if (this._fillRender) {
            this._fillRender.clear();
        }
        //
        this._testCommandIndex = 0;
        this._testMedicanIndex = 0;
        this._testMedicansHitArray = [];
        this._isInTest = false;
        this._isWaitingForTest = false;
        //
        this._commandIndex = 0;
        this._areaIndex = 0;
        this._strokeIndex = 0;
        this._medianIndex = 0;
        //
        this._hanziRender.fillColor = this.hanziFillColor;
    },

    reset () {
        this.resetRender();
        this._commandCount = 0;
        this._areaCount = 0;
        this._medianIndex = 0;
        this.offset = null;
        this._svgCommandObject = null;
        this._svgStrokeObject = null;
        this._svgObject = null;
    },

    updateConfiguration() {
        this.configuration = {
            "segments"                  : this.segments,
            "flipX"                     : (this.flipX === null ? false : this.flipX),
            "flipY"                     : (this.flipY === null ? false : this.flipY),
            "offset"                    : this.offset ? this.offset : cc.v2(0, 0),
            "dataScale"                 : (this.__dataScale === null ? 1.0 : this.__dataScale)
        };
    },

    updateSVGObject () {
        this.resetHanziRender();
        this.updateConfiguration();
        if (!this.hzWriterJson) {
            return;
        }
        this._svgObject = ssr.SVG.Parser.parseHZWriterJSON(this.hzWriterJson.json, this.configuration);
        this._updateContentSize();
        this._commandCount = this._svgObject.pathCount;

        // cc.log(this._svgObject);
    },

    _updateContentSize () {
        this.configuration.dataScale = this.__dataScale = this.node.width / this.__dataWidth;
        this.offset = this.configuration.offset = cc.v2(this.node.width / 2, -124 * this.__dataScale + this.node.height / 2);
    },

    _getMedicanPos (commandIndex, medianIndex) {
        let svgCommandObject = this._svgObject.commandArray[commandIndex - 1];
        let median = svgCommandObject.median[medianIndex - 1];
        let x = (this.configuration.flipX ? -median[0] * this.configuration.dataScale + this.configuration.offset.x : median[0] * this.configuration.dataScale - this.configuration.offset.x);
        let y = (this.configuration.flipY ? -median[1] * this.configuration.dataScale + this.configuration.offset.y : median[1] * this.configuration.dataScale - this.configuration.offset.y);
        return {"x": x, "y" : y};
    },

    _getDisBetweenTouchAndTargetMedian (touchLoc, commandIndex, medianIndex) {
        let medicanPos = this._getMedicanPos(commandIndex, medianIndex);
        return cc.v2(touchLoc.x - medicanPos.x, touchLoc.y - medicanPos.y).mag();
    },

    testStart () {
        if (this._isInTest) {
            return;
        }
        //
        this._hanziRender.lineWidth = this.hanziOutlineWidth;
        this._hanziRender.strokeColor = this.hanziOutlineColor;
        this._hanziRender.fillColor = cc.color(0, 0, 0, 0);
        this.drawHanzi();
        //
        this._isInTest = true;
        this._testCommandIndex = 1;
        this._testMedicanIndex = 1;
        //
        this.node.on(cc.Node.EventType.TOUCH_START, this._touchStartCallback, this);
        this.node.on(cc.Node.EventType.TOUCH_MOVE, this._touchMoveCallback, this);
        this.node.on(cc.Node.EventType.TOUCH_END, this._touchEndCallback, this);
        //
        cc.Component.EventHandler.emitEvents(this.cbTestStart, this);
        cc.Component.EventHandler.emitEvents(this.cbTestOneStrokeStart, this, {"index": this._testCommandIndex});
        cc.Component.EventHandler.emitEvents(this.cbTestOneMedianStart, this, {"index": this._testMedicanIndex, "total": this._svgObject.commandArray[this._testCommandIndex - 1].median.length});
    },

    testEnd () {
        if (!this._isInTest) {
            return;
        }
        this._isInTest = false;
        this.node.off(cc.Node.EventType.TOUCH_START, this._touchStartCallback, this);
        this.node.off(cc.Node.EventType.TOUCH_MOVE, this._touchMoveCallback, this);
        this.node.off(cc.Node.EventType.TOUCH_END, this._touchEndCallback, this);
        //
        cc.Component.EventHandler.emitEvents(this.cbTestEnd, this, {"result": (this._testCommandIndex > this._svgObject.pathCount ? 1 : 0)});
    },

    _touchStartCallback (event) {
        this._isWaitingForTest = false;
        var touches = event.getTouches();
        var touchLoc = touches[0].getLocation();
        touchLoc = this.node.convertToNodeSpaceAR(touchLoc);
        this._testRender.clear();  
        this._testRender.moveTo(touchLoc.x, touchLoc.y);
    },
    
    _touchMoveCallback (event) {
        if (!this._isInTest || this._isWaitingForTest) {
            return;
        }
        var touches = event.getTouches();
        var touchLoc = touches[0].getLocation();
        touchLoc = this.node.convertToNodeSpaceAR(touchLoc);
        this._testRender.lineTo(touchLoc.x, touchLoc.y);
        this._testRender.stroke();
        this._testRender.moveTo(touchLoc.x, touchLoc.y);

        let medicanPos = this._getDisBetweenTouchAndTargetMedian(touchLoc, this._testCommandIndex, this._testMedicanIndex);
        if (medicanPos <= this._testAccuracy) {
            this._testMedicanIndex += 1;
            let totalMedians = this._svgObject.commandArray[this._testCommandIndex - 1].median.length;
            cc.Component.EventHandler.emitEvents(this.cbTestOneMedianFinish, this, {"index": this._testMedicanIndex - 1, "total": totalMedians});
            if (this._testMedicanIndex > totalMedians) {
                cc.Component.EventHandler.emitEvents(this.cbTestOneStrokeEnd, this, {"index": this._testCommandIndex, "result": 1});
                this._testRender.clear();
                this._isWaitingForTest = true;
                this._fillTestStroke(this._testCommandIndex);
                this._testCommandIndex += 1;
                this._testMedicanIndex = 1;
                if (this._testCommandIndex > this._svgObject.pathCount) {
                    this.testEnd();
                }
            }
            else {
                cc.Component.EventHandler.emitEvents(this.cbTestOneMedianStart, this, {"index": this._testMedicanIndex, "total": totalMedians});
            }
        }
    },

    _touchEndCallback (event) {
        if (!this._isInTest || this._isWaitingForTest) {
            return;
        }
        var touches = event.getTouches();
        var touchLoc = touches[0].getLocation();
        touchLoc = this.node.convertToNodeSpaceAR(touchLoc);
        this._testRender.clear();

        cc.Component.EventHandler.emitEvents(this.cbTestOneStrokeEnd, this, {"index": this._testCommandIndex, "result": 0});

        this._isWaitingForTest = true;
        this._testMedicanIndex = 1;
    },

    _drawHintCommand(commandIndex) {
        this._hintRender.clear();
        let svgCommandObject = this._svgObject.commandArray[commandIndex - 1];
        for (var i = 0; i < svgCommandObject.areaArray.length; i++) {
            let area = svgCommandObject.areaArray[i];
            for (var j = 0; j < area.strokeArray.length; j++) {
                ssr.SVG.Util.Render.draw(this._hintRender, area.strokeArray[j], this.configuration, false);
            }
        }
    },

    hintStroke (commandIndex) {
        if (!this._isInTest) {
            return;
        }
        if (!commandIndex) {
            commandIndex = this._testCommandIndex;
        }
        if (commandIndex > this._commandCount) {
            return;
        }
        if (commandIndex == 0) {
            commandIndex = 1;
        }
        cc.Component.EventHandler.emitEvents(this.cbTestHintStart, this);
        cc.tween(this.node)
            .call(() => { this._drawHintCommand(commandIndex); })
            .delay(this.hintSpeed)
            .call(() => { this._clearHint(commandIndex); })
            .delay(this.hintSpeed)
            .call(() => { this._drawHintCommand(commandIndex); })
            .delay(this.hintSpeed)
            .call(() => { this._clearHint(commandIndex); })
            .delay(this.hintSpeed)
            .call(() => { this._drawHintCommand(commandIndex); })
            .delay(this.hintSpeed)
            .call(() => { this._clearHint(commandIndex); cc.Component.EventHandler.emitEvents(this.cbTestHintEnd, this); })
            .start()
    },

    _clearHint(commandIndex) {
        this._hintRender.clear();
    },
});
